﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Extensions.DependencyModel;
using System.Collections.Immutable;
using System.Security.Cryptography.X509Certificates;
using NFinal.Common.Plugin.Exceptions;

namespace NFinal.Common.Plugin
{
    /// <summary>
    /// 插件程序集加载类
    /// </summary>
    public class PluginLoadContext : AssemblyLoadContext
    {
        private AssemblyDependencyResolver _resolver;
        private SortedList<string, string> commonAssemblyPathList = new SortedList<string, string>();
        private SortedList<string, string> commonNativeDllPathList = new SortedList<string, string>();

        /// <summary>
        /// 插件上下文加载 
        /// </summary>
        /// <param name="pluginFolder">插件主目录必须带*.deps.json依赖文件</param>
        /// <param name="commonAssemblyFolders">插件其它公用dll目录</param>
        public PluginLoadContext(Assembly hostAssembly, string pluginFolder, params string[] commonAssemblyFolders) : base(isCollectible: true)
        {
            HostContext.GetHostRefAssemblyNames(hostAssembly);
            this.ResolvingUnmanagedDll += PluginLoadContext_ResolvingUnmanagedDll;
            this.Resolving += PluginLoadContext_Resolving;
            this.Unloading += PluginLoadContext_Unloading;
            string dependencyJsonFile = Directory.GetFiles(pluginFolder, "*.deps.json").FirstOrDefault();
            if (dependencyJsonFile == null)
            {
                throw new FileNotFoundException("插件目录中找不到.deps.json依赖文件");
            }
            using (var dependencyFileStream = File.OpenRead(dependencyJsonFile))
            {
                using (DependencyContextJsonReader dependencyContextJsonReader = new DependencyContextJsonReader())
                {
                    var dependencyContext = dependencyContextJsonReader.Read(dependencyFileStream);
                    string currentRuntimeIdentifier= dependencyContext.Target.Runtime;
                    string rootRuntimeIdentifier = null;
                    var target = dependencyContext.Target;

                    if (!string.IsNullOrEmpty(target.Runtime))
                    {
                        currentRuntimeIdentifier = target.Runtime;
                    }
                    else
                    {
                        //if (!RuntimeIdentifier.IsFitNetCore30(target.Framework))
                        //{
                        //    throw new AssemblyDependencyException($"插件目录是{target.Framework}.目录中缺少必要dll文件." +
                        //        $"请尝试修改项目文件为:<TargetFrameworks>{RuntimeIdentifier.GetFrameworkIdentity(target.Framework)};netcoreapp2.1</TargetFrameworks>" +
                        //        $"<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>,然后发布为.netcoreapp2.1项目");
                        //}
                        //else
                        //{
                            (rootRuntimeIdentifier, currentRuntimeIdentifier) = RuntimeIdentifier.GetCurrentRuntime();
                        //}
                    }
                    var assemblyNames= dependencyContext.RuntimeLibraries.Select(x => x.Name).ToList();
                    var dependencyAssemblyNames = 
                    DictionaryHelper.ToDictionary(
                        dependencyContext.RuntimeLibraries.Where(x=>!x.Name.StartsWith("runtime.")),
                        k=>k.Name);

                    var dependencyNativeFiles = DictionaryHelper.ToDictionary(
                        dependencyContext.RuntimeLibraries.Where(x => x.Name.StartsWith("runtime.")),
                        x => x.Name.Substring(15));

                    //加载插件目录下的dll
                    LoadFromDirectory(pluginFolder, dependencyAssemblyNames, dependencyNativeFiles);
                    //加载runtimes下的dll(平台相关)
                    LoadRuntimes(pluginFolder,rootRuntimeIdentifier, currentRuntimeIdentifier, target.Framework, dependencyAssemblyNames, dependencyNativeFiles);

                    //加载其它目录下的dll
                    foreach (var commonPath in commonAssemblyFolders)
                    {
                        if (Directory.Exists(commonPath))
                        {
                            LoadFromDirectory(commonPath, dependencyAssemblyNames, dependencyNativeFiles);
                            //加载runteimes下的dll(平台相关)
                            LoadRuntimes(commonPath, rootRuntimeIdentifier, currentRuntimeIdentifier, target.Framework, dependencyAssemblyNames, dependencyNativeFiles);
                        }
                    }
                }
            }
            _resolver = new AssemblyDependencyResolver(pluginFolder);
        }

        private void PluginLoadContext_Unloading(AssemblyLoadContext obj)
        {
            this._resolver = null;
            this.commonAssemblyPathList.Clear();
            this.commonAssemblyPathList = null;
            this.commonNativeDllPathList.Clear();
            this.commonNativeDllPathList = null;
        }


        /// <summary>
        /// 加载runtimes(平台相关dll)
        /// </summary>
        /// <param name="pluginFolder">插件</param>
        /// <param name="runtimeIdentifier">平台标识</param>
        /// <param name="framework">框架</param>
        /// <param name="dependencyAssemblyNames">依赖的程序集</param>
        /// <param name="dependencyNativeFiles">依赖的native dll</param>
        private void LoadRuntimes(string pluginFolder,string rootRuntimeIdentifier,string runtimeIdentifier,string framework,
            IDictionary<string, RuntimeLibrary> dependencyAssemblyNames,
            IDictionary<string, RuntimeLibrary> dependencyNativeFiles)
        {
            //加载环境包
            string pluginRuntimePath = Path.Combine(pluginFolder, "runtimes");
            if (Directory.Exists(pluginRuntimePath))
            {
                foreach (var pluginRuntimeFolderPath in
                    Directory.GetDirectories(pluginRuntimePath, "*", SearchOption.TopDirectoryOnly))
                {
                    string pluginRuntimeFolderName = Path.GetFileName(pluginRuntimeFolderPath);
                    bool isFit = false;
                    if (pluginRuntimeFolderName == rootRuntimeIdentifier)
                    {
                        isFit = true;
                    }
                    else
                    {
                        isFit = Plugin.RuntimeIdentifier.IsBelongToRuntime(pluginRuntimeFolderName, runtimeIdentifier);
                    }
                    if (isFit)
                    {
                        string pluginRuntimeLibPath = Path.Combine(pluginRuntimePath, pluginRuntimeFolderName, "lib", framework);
                        if (Directory.Exists(pluginRuntimeLibPath))
                        {
                            foreach (var pluginRuntimeLibFile in
                                Directory.GetDirectories(pluginRuntimeLibPath, "*.dll", SearchOption.TopDirectoryOnly))
                            {
                                var assemblyName = AssemblyName.GetAssemblyName(pluginRuntimeLibFile);
                                //if (dependencyAssemblyNames.ContainsKey(assemblyName.Name))
                                {
                                    commonAssemblyPathList.TryAdd(assemblyName.Name, pluginRuntimeLibFile);
                                }
                            }
                        }
                        //RuntimeInformation.FrameworkDescription;
                        string pluginRuntimeNativePath = Path.Combine(pluginRuntimePath, pluginRuntimeFolderName, "native");
                        if (Directory.Exists(pluginRuntimeNativePath))
                        {
                            foreach (var pluginRuntimeNativeFile in
                                Directory.GetFiles(pluginRuntimeNativePath, "*.dll", SearchOption.TopDirectoryOnly))
                            {
                                string nativeName = Path.GetFileNameWithoutExtension(pluginRuntimeNativeFile);
                                //if (dependencyNativeFiles.ContainsKey(nativeName))
                                {
                                    string pluginRuntimeNativeFileCopy = Path.Combine(pluginFolder, Path.GetFileName(pluginRuntimeNativeFile));
                                    //File.Copy(pluginRuntimeNativeFile, pluginRuntimeNativeFileCopy, true);
                                    commonNativeDllPathList.TryAdd(nativeName, pluginRuntimeNativeFileCopy);
                                }
                            }
                        }
                    }
                }
            }
        }
        
        /// <summary>
        /// 加载程序集
        /// </summary>
        /// <param name="pluginFolder"></param>
        /// <param name="dependencyAssemblyNames"></param>
        /// <param name="dependencyNativeFiles"></param>
        private void LoadFromDirectory(string pluginFolder,
            IDictionary<string,RuntimeLibrary> dependencyAssemblyNames,
            IDictionary<string, RuntimeLibrary> dependencyNativeFiles)
        {
            foreach (var file in Directory.GetFiles(pluginFolder, "*.dll", SearchOption.TopDirectoryOnly))
            {
                try
                {
                    var assemblyName = AssemblyName.GetAssemblyName(file);
                    commonAssemblyPathList.TryAdd(assemblyName.Name, file);
                }
                catch (BadImageFormatException)
                {
                    string dllName = Path.GetFileNameWithoutExtension(file);
                    commonNativeDllPathList.TryAdd(dllName, file);
                }
            }
        }

        /// <summary>
        /// 加载失败后,再使用系统默认的加载器加载
        /// </summary>
        /// <param name="assemblyLoadContext"></param>
        /// <param name="assemblyName"></param>
        /// <returns></returns>
        private Assembly PluginLoadContext_Resolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName)
        {
            string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
            if (assemblyPath != null)
            {
                //如果主程序集已经有的同样名称,同样版本的程序集则不加载
                if (HostContext.HasAssembly(assemblyName))
                {
                    return null;
                }
                using (var stream = new System.IO.FileStream(assemblyPath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    return assemblyLoadContext.LoadFromStream(stream);
                }
            }
            return null;
        }
        /// <summary>
        /// 加载失败后,再使用系统默认的加载器加载
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="unmanagedDllName"></param>
        /// <returns></returns>
        private IntPtr PluginLoadContext_ResolvingUnmanagedDll(Assembly assembly, string unmanagedDllName)
        {
            string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
            if (libraryPath != null)
            {
                return LoadUnmanagedDllFromPath(libraryPath);
            }
            return IntPtr.Zero;
        }

        /// <summary>
        /// 先加载插件目录下的程序集,
        /// </summary>
        /// <param name="assemblyName"></param>
        /// <returns></returns>
        protected override Assembly Load(AssemblyName assemblyName)
        {
            if (commonAssemblyPathList.TryGetValue(assemblyName.Name, out string assemblyPath))
            {
                if (HostContext.HasAssembly(assemblyName))
                {
                    return null;
                }
                using (var stream = new System.IO.FileStream(assemblyPath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    return this.LoadFromStream(stream);
                }
            }
            return null;
        }

        /// <summary>
        /// 先加载插件目录下的程序集
        /// </summary>
        /// <param name="unmanagedDllName"></param>
        /// <returns></returns>
        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
        {
            if (commonNativeDllPathList.TryGetValue(unmanagedDllName, out string dllFilePath))
            {
                return LoadUnmanagedDllFromPath(dllFilePath);
            }
            return IntPtr.Zero;
        }
    }
}
